package com.flexfiltering.view.controls 
{
	
import com.flexfiltering.view.util.CollectionUtil;

import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
import flash.text.TextFieldAutoSize;
import flash.ui.Keyboard;

import mx.collections.ArrayCollection;
import mx.controls.Spacer;
import mx.core.Container;
import mx.core.EdgeMetrics;
import mx.core.IBorder;
import mx.core.ScrollPolicy;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.ListEvent;

use namespace mx_internal;

/**
 *
 */
[Style(name="gap", type="Number", inherit="no" )]

public class TokenField extends CompletionInput
{
	// @------------------------------------------------------ STATIC PROPERTIES
	// @-------------------------------------------------------PUBLIC PROPERTIES
	
	public var tokenizingKeys:Array;
	
	//----------------------------------
	//  dataProvider
	//----------------------------------
	
	private var collection:ArrayCollection;
	
	/**
	 *  @private
	 *  Change-flag for values property.
	 */
	protected var valuesChanged:Boolean;
	
	[Bindable()]
	/**
	 *  
	 * 
	 * 	@default 
	 */ 
	public function get dataProvider() : Object
	{
	    return collection;
	}
	
	/**
	 *  @private
	 */
	public function set dataProvider ( val:Object ) : void
	{
		if ( collection )
			collection.removeEventListener( CollectionEvent.COLLECTION_CHANGE, 
											collectionChangeHandler );
		
		collection = CollectionUtil.objectToCollection(val) as ArrayCollection;
		
		collection.addEventListener( CollectionEvent.COLLECTION_CHANGE, 
									 collectionChangeHandler,
									 false, 0, true );
		
		var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
        	event.kind = CollectionEventKind.RESET;
        
        collectionChangeHandler(event);
        dispatchEvent(event);
	}
	
	//----------------------------------
	//  caratIndex
	//----------------------------------

	/**
	 *  @private
	 *  Change-flag for caratIndex property.
	 */
	private var caratIndexChanged:Boolean;

	/**
	 *  @private
	 *  Storage for the caratIndex property.
	 */
	private var _caratIndex:int = 0;

	[Bindable()]
	/**
	 *  
	 * 
	 * 	@default 
	 */ 
	public function get caratIndex() : int
	{
	    return _caratIndex;
	}

	/**
	 *  @private
	 */
	public function set caratIndex ( val:int ) : void
	{
		if ( caratIndex != val )
		{
			_caratIndex = val;
			caratIndexChanged = true;
			invalidateDisplayList();	
		}
	}
	
	// @---------------------------------------------------- INTERNAL PROPERTIES
	
	protected var tokens:Array = [];
	protected var selectedTokens:Array = [];
	protected var selectedTokensChanged:Boolean;
	
	protected var tokenContainer:Container;
	protected var spacer:Spacer;
	
	// @-------------------------------------------------- OVERRIDDEN PROPERTIES
	// @------------------------------------------------------------ CONSTRUCTOR
	
	public function TokenField()
	{
		super();
		addEventListener(KeyboardEvent.KEY_DOWN,keyDownHandler);
						  
		dataProvider = new ArrayCollection();
		labelField = "label";
		
		// keycode 188 is a "," character
		tokenizingKeys = [ Keyboard.RIGHT, 188 ];
	}
	
	// @--------------------------------------------------------- PUBLIC METHODS
	
	//----------------------------------
	//  moveCaratRight
	//----------------------------------
	
	public function moveCaratRight () : void
	{
		if (collection)
		{
			caratIndex = Math.min(collection.length,caratIndex+1);
		}
	}
	
	//----------------------------------
	//  moveCaratLeft
	//----------------------------------
	
	public function moveCaratLeft () : void
	{
		caratIndex = Math.max(0,caratIndex-1);
	}
	
	// @------------------------------------------------------- INTERNAL METHODS
	
	//----------------------------------
   	//  selectToken
    //----------------------------------
	
	protected function selectToken ( token:Token,
									 deselectOthers:Boolean=false ) : void
	{
		if ( token == null )
			return;
			
		if ( deselectOthers )
		{
			clearSelections();
		}
		
		_caratIndex = tokens.indexOf(token);
		
		selectedTokens.push(token);
		selectedTokensChanged = true;
		invalidateProperties();
		validateNow();	
	}
	
	//----------------------------------
   	//  selectAllTokens
    //----------------------------------
	
	protected function selectAllTokens () : void
	{
		selectedTokens = tokens;
		selectedTokensChanged = true;
		invalidateProperties();
	}
	
	
	//----------------------------------
   	//  deselectAllTokens
    //----------------------------------
	
	protected function clearSelections () : void
	{
		selectedTokens = [];
		selectedTokensChanged = true;
		invalidateProperties();
	}
	
	//----------------------------------
   	//  removeToken
    //----------------------------------
	
	protected function removeToken ( token:Token ) : void
	{
		var idx:int = collection.getItemIndex(token.data);
		if (collection)
		{
			(collection as ArrayCollection).removeItemAt(idx);
		}
	}
	
	//----------------------------------
	//  clearText
	//----------------------------------
	
	protected function clearText () : void
	{
		text = "";
	}
	
	// @----------------------------------------------------- OVERRIDDEN METHODS
	
	//----------------------------------
	//  acceptSuggestion
	//----------------------------------
	
	override protected function acceptSuggestion ():void
	{
		if (completions.length > 0 && text != "")
		{
			collection.addItemAt(getMenu().selectedItem,caratIndex);
			callLater(clearText);
			displayMenu(false);
		}
	}
		
	//----------------------------------
	//  createChildren
	//----------------------------------
	
	override protected function createChildren():void
	{
		super.createChildren();
		
		if ( !tokenContainer )
		{
			tokenContainer = new Container();
			tokenContainer.horizontalScrollPolicy = ScrollPolicy.OFF;
			tokenContainer.verticalScrollPolicy = ScrollPolicy.OFF;
			tokenContainer.clipContent = true;
			addChild(tokenContainer);
			
			spacer = new Spacer();
			spacer.focusEnabled = false;
			spacer.tabEnabled = false;
			tokenContainer.addChild(spacer);
		}
		
		textField.autoSize = TextFieldAutoSize.LEFT;
		textField.addEventListener(Event.CHANGE, textField_changeHandler, false, 100);
		textField.border = false;
	}
	
	
	//----------------------------------
	//  commitProperties
	//----------------------------------
	
	override protected function commitProperties():void
	{
		super.commitProperties();
		
		if ( valuesChanged )
		{
			// for now we're just going to remove all the tokens and add
			// a token for each item in the dataProvider.  If this proves
			// to perform poorly, we can do something much more intelligent.
			
			var t:Token;
			
			if ( tokens && tokens.length > 0 )
			{
				for each ( t in tokens )
				{
					tokenContainer.removeChild(t);
				}
			}
			
			tokens = [];
			
			// add a token for each selectedItem
			for ( var i:int=0; i<dataProvider.length; i++ )
			{
				t = new Token();
				t.index = i;
				t.data = collection.getItemAt(i);
				t.label = getMenu().itemToLabel(collection.getItemAt(i));
				t.focusEnabled = false;
				t.styleName = getStyle('tokenStyleName');
				
				t.addEventListener("deleteClick",token_auxillaryClickHandler,
								   false,0,true);
								   
				t.addEventListener("click",token_clickHandler,
								   false,0,true);
				
				tokens.push(t);
				
				tokenContainer.addChild(t);
			}
			
			invalidateSize();
			invalidateDisplayList();
			
			valuesChanged = false;
		}
		
		if ( selectedTokensChanged )
		{
			for each ( t in tokens )
			{
				t.selected = selectedTokens.indexOf(t) != -1
			}
			
			selectedTokensChanged = false;
		}
	}
	
	//----------------------------------
	//  measure
	//----------------------------------	
	
	override protected function measure():void
	{
		super.measure();
		
		// right now this just measures like a TextField, which basically means
		// it doesn't measure based on the contents.  This is sufficient for now.
	}
	
	//----------------------------------
	//  updateDisplayList
	//----------------------------------
	
	override protected function updateDisplayList(w:Number,h:Number):void
	{
		super.updateDisplayList(w,h);
		
		this.scrollRect = new Rectangle(0,0,w,h);
		
		var bm:EdgeMetrics = border && border is IBorder ?
	                         IBorder(border).borderMetrics :
	                         EdgeMetrics.EMPTY;
	                    
	    var pm:EdgeMetrics = new EdgeMetrics ( getStyle('paddingLeft'),
            								   getStyle('paddingTop'),
            								   getStyle('paddingRight'),
            								   getStyle('paddingBottom') );
            								   
        var gap:Number = getStyle('gap') || 2;
            								   
        var _x:Number = 0;
        
        var viewWidth:Number = w-bm.left-bm.right-pm.left-pm.right;
        var viewHeight:Number = h-bm.top-bm.bottom-pm.top-pm.bottom;
        
        tokenContainer.x = bm.left + pm.left;
        tokenContainer.y = bm.top + pm.top;
        tokenContainer.setActualSize( viewWidth, viewHeight)
        							  
        for ( var i:int=0; i<=tokens.length; i++ )
        {
        	if ( i == caratIndex )
        	{
        		textField.x = _x + bm.left + pm.left;

        		textField.height = viewHeight;
        		
        		_x += textField.width;
        		
    			spacer.width = textField.width;
    			spacer.x = textField.x - bm.left - pm.left;
        	}
        	
        	if ( i < tokens.length )
        	{
	        	var t:Token = tokens[i] as Token;
	        	
	        	t.setActualSize( t.getExplicitOrMeasuredWidth(),
	        					 tokenContainer.height );

	        	t.x = _x;
	        	t.y = 0;
	        	
	        	_x += t.width + gap;
	        }
        }
        
        tokenContainer.validateNow();
        
        var offset:Number;
   		
        if ( textField.x  > width - bm.right - pm.right )
        {
	        offset = textField.x - width - bm.right - pm.right;
        						
        	if ( Math.ceil(offset/30)*30 > tokenContainer.maxHorizontalScrollPosition )
        	{
        		tokenContainer.horizontalScrollPosition = 
        			tokenContainer.maxHorizontalScrollPosition;
        	}
        	else
        	{
        		tokenContainer.horizontalScrollPosition =  
        			Math.ceil(offset/30)*30;
        	}
        }
        else if ( textField.x <= bm.left + pm.left )
        {
        	offset = (textField.x) - ( bm.left - pm.left )
        	
        	if ( Math.ceil(offset/30)*30 > tokenContainer.maxHorizontalScrollPosition )
        	{
        		tokenContainer.horizontalScrollPosition = 
        			tokenContainer.maxHorizontalScrollPosition;
        	}
        	else
        	{
        		tokenContainer.horizontalScrollPosition = 
        			Math.ceil(offset/30)*30;
        	}
        }
        
        if ( textField.x + textField.width > w - bm.right - pm.right )
        {
        	tokenContainer.horizontalScrollPosition = 
        		Math.min( tokenContainer.horizontalScrollPosition + textField.width + 4,
        				  tokenContainer.maxHorizontalScrollPosition );
        	
        }
        
        textField.x -= tokenContainer.horizontalScrollPosition;
        textField.y = (h - textField.height)/2;
	}
	
	// @--------------------------------------------------------- EVENT HANDLERS
	
	//----------------------------------
	//  collectionChangeHandler
	//----------------------------------
	
	protected function collectionChangeHandler ( event:CollectionEvent ) : void
	{
		valuesChanged = true;
		invalidateProperties();
		
		if ( event.kind == CollectionEventKind.ADD )
		{
			caratIndex = event.location + 1;
		}
		
		if ( event.kind == CollectionEventKind.REMOVE )
		{
			caratIndex = event.location;
		}
		
		if ( event.kind == CollectionEventKind.RESET )
		{
			caratIndex = collection.length;
		}
	}
	
	//----------------------------------
	//  textField_changeHandler
	//----------------------------------
	
	protected function textField_changeHandler ( event:Event ) : void
	{
		invalidateDisplayList();
	}
	
	//----------------------------------
	//  token_auxillaryClickHandler
    //----------------------------------
	
	protected function token_auxillaryClickHandler ( event:Event ) : void
	{
		removeToken(event.target as Token);
	}
	
	//----------------------------------
	//  token_clickHandler
    //----------------------------------
	
	protected function token_clickHandler ( event:MouseEvent ) : void
	{
		selectToken(event.target as Token, event.shiftKey != true );
	}
	
	// @---------------------------------------------- OVERRIDDEN EVENT HANDLERS
	
	//----------------------------------
	//  changeHandler
	//----------------------------------
	
	override protected function changeHandler(event:Event):void
	{
		super.changeHandler(event);
		clearSelections();
	}
	
	//----------------------------------
	//  keyDownHandler
	//----------------------------------
	
	override protected function keyDownHandler ( event:KeyboardEvent ) : void
	{
		super.keyDownHandler(event);
		
		if ( event.keyCode == 4294967295 )
		{
			selectAllTokens();
			return;
		}
		
		var tokenize:Boolean = false;
		
		for each ( var k:int in tokenizingKeys )
		{
			if ( event.keyCode == k )
			{
				tokenize = true;
				break;
			}
		}
		
		if ( text != "" && tokenize )
		{
			event.keyCode = Keyboard.ENTER;
			event.preventDefault();
            event.stopPropagation();
            this.dispatchEvent(event);
            return;
		}
		
		if ( event.keyCode == Keyboard.LEFT )
		{
			if ( textField.caretIndex == 0 )
			{
				clearSelections();
				moveCaratLeft()
			}
		}
		
		if ( event.keyCode == Keyboard.RIGHT )
		{
			if ( textField.caretIndex == textField.length )
			{
				clearSelections();
				moveCaratRight()
			}
		}
		
		if ( event.keyCode == Keyboard.DELETE || 
			 event.keyCode == Keyboard.BACKSPACE )
		{
			if ( selectedTokens.length > 0 )
			{
				for each ( var t:Token in selectedTokens )
				{
					removeToken(t);
				}
				
				selectedTokens = [];
			}
			else if ( textField.caretIndex == 0 )
			{
				selectToken(tokens[caratIndex-1],true);
			}
		}
	}
	
	//----------------------------------
	//  focusInHandler
	//----------------------------------
	
	override protected function focusInHandler(event:FocusEvent):void
	{
		super.focusInHandler(event);
		selectAllTokens();
	}
	
	//----------------------------------
	//  focusOutHandler
	//----------------------------------
	
	override protected function focusOutHandler(event:FocusEvent):void
	{
		super.focusOutHandler(event);
		clearSelections();
		if ( getMenu().visible )
		{
			acceptSuggestion();
		}
		else
		{
			clearText();
		}
	}
	
} // class
} 